/*
 * Copyright (c) 2009, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.wso2.carbon.bpel.b4p.extension;

import org.apache.ode.bpel.o.OPartnerLink;
import org.apache.ode.bpel.o.OProcess;
import org.apache.ode.bpel.runtime.BpelRuntimeContext;
import org.apache.ode.bpel.runtime.extension.ExtensionContext;
import org.apache.ode.bpel.common.FaultException;
import org.apache.ode.bpel.dd.DeployDocument;
import org.apache.ode.bpel.dd.TDeployment;
import org.apache.ode.bpel.dd.TInvoke;
import org.apache.ode.bpel.dd.TProvide;
import org.apache.ode.store.DeploymentUnitDir;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axiom.om.OMElement;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;

import javax.xml.namespace.QName;
import javax.wsdl.Definition;
import javax.wsdl.Service;
import javax.wsdl.Port;
import javax.wsdl.Operation;
import java.io.File;
import java.util.List;

import com.ibm.wsdl.extensions.soap.SOAPAddressImpl;

public class PeopleActivity {
    protected final Log log = LogFactory.getLog(PeopleActivity.class);

    private String name;
    private String inputVarName;
    private String outputVarName;
    private boolean isSkipable = false;

    private String partnerLinkName;
    private String operation;
    private String responseOperation;

    private EndpointReference targetEPR;
    private String serviceURI;
    private String servicePort;
    private String callbackServicePort;
    private OMElement payload = null;

    private ExtensionContext extensionContext;
    private Element element;

    private DeploymentUnitDir du;

    private InteractionType activityType;

    public InteractionType getActivityType() {
        return activityType;
    }

    public QName getCallbackServiceName() {
        return callbackServiceName;
    }

    public String getCallbackServicePort() {
        return callbackServicePort;
    }

    QName serviceName;
    QName callbackServiceName;
    Definition hiWSDL;

    public PeopleActivity (ExtensionContext extensionContext, Element element) throws FaultException {
        this.extensionContext = extensionContext;
        this.element = element;
        init();
        deriveServiceEPR();
    }

    public Element getInputMessage() throws FaultException {
        Node inputNode = extensionContext.readVariable(inputVarName);

        if (inputNode.getNodeType() == Node.ELEMENT_NODE) {
            return (Element)inputNode;
        } else {
            log.error("The node type of the variable is not ELEMENT");
            throw new FaultException(new QName(B4PExtensionBundle.NS, "Unsupported variable type"));
        }
    }

    public Operation getOperation() {
        BpelRuntimeContext runTimeContext = extensionContext.getInternalInstance();

        OProcess process = runTimeContext.getProcessModel();

        OPartnerLink  partnerLink = process.getPartnerLink(partnerLinkName);

        return partnerLink.getPartnerRoleOperation(operation);
    }

    public Operation getCallbackOperation() {
        BpelRuntimeContext runTimeContext = extensionContext.getInternalInstance();

        OProcess process = runTimeContext.getProcessModel();

        OPartnerLink partnerLink = process.getPartnerLink(partnerLinkName);

        return partnerLink.getMyRoleOperation(responseOperation);

    }

    public String getOperationName() {
        return operation;
    }

    public String getEPRURL() {
        return serviceURI;
    }

    public String getServicePort() {
        return servicePort;
    }

    public String getOutputVarName() {
        return outputVarName;
    }

    public DeploymentUnitDir getDu() {
        return du;
    }

    public QName getServiceName() {
        return serviceName;
    }

    public Definition getHiWSDL() {
        return hiWSDL;
    }

    public Definition getCallbackWSDL() {
        return du.getDefinitionForService(callbackServiceName);
    }
    
    private void init() throws FaultException {
        if (!element.getLocalName().equals("peopleActivity") || !element.getNamespaceURI().equals(B4PExtensionBundle.NS)) {
            throw new FaultException(new QName(B4PExtensionBundle.NS, "no peopleActivity found"));
        }

        name = element.getAttribute("name");
        inputVarName = element.getAttribute("inputVariable");
        outputVarName = element.getAttribute("outputVariable");
        isSkipable = element.getAttribute("isSkipable").equalsIgnoreCase("yes");

        NodeList taskList = element.getChildNodes();

        Node task = null;
        int elementNodeCount = 0;
        for (int i = 0; i < taskList.getLength(); i++) {
            if (taskList.item(i).getNodeType() == Node.ELEMENT_NODE) {
                elementNodeCount++;
                if (elementNodeCount > 1) {
                    break;
                }
                task = taskList.item(i);
            }
        }

        if (elementNodeCount != 1 || task == null) {
            log.error("Invalid peopleActivity definition");
            throw new FaultException(new QName(B4PExtensionBundle.NS, "Invalid peopleActivity definition"));
        }

        /*TODO this only checks for b4p namespace, we need to check for <htd:task>...</htd:task> and
         <htd:notification>...</htd:notification> which r in ht namespace*/
        if (!task.getNamespaceURI().equals(B4PExtensionBundle.NS)) {
            throw new FaultException(new QName(B4PExtensionBundle.NS, "Invalid namespace uri for the task. " +
                    "The valid namespace: " + B4PExtensionBundle.NS));
        }
        //TODO is it required to read through the element per task type? cant we jst do it once? what about null elements 
        if (task.getLocalName().equals("remoteTask")) {
            activityType = InteractionType.TASK;
            if (task.getNodeType() == Node.ELEMENT_NODE) {
                Element remoteTaskEle = (Element)task;
                partnerLinkName = remoteTaskEle.getAttribute("partnerLink");
                operation = remoteTaskEle.getAttribute("operation");
                responseOperation = remoteTaskEle.getAttribute("responseOperation");
                if (log.isDebugEnabled()) {
                    log.debug("name: " + name + " inputVarName: " + inputVarName + " outPutVarName: " + outputVarName +
                            " isSkipable: " + isSkipable + " partnerLinkName: " + partnerLinkName + " operation: " +
                            operation + " responseOperation: " + responseOperation);
                }
            } //TODO what if NODE type is not ELEMENT_NODE
        } else if (task.getLocalName().equals("remoteNotification")) {
            activityType = InteractionType.NOTIFICATION;
            if (task.getNodeType() == Node.ELEMENT_NODE) {
                Element remoteTaskEle = (Element)task;
                partnerLinkName = remoteTaskEle.getAttribute("partnerLink");
                operation = remoteTaskEle.getAttribute("operation");
                if (log.isDebugEnabled()) {
                    log.debug("name: " + name + " inputVarName: " + inputVarName + " partnerLinkName: " +
                            partnerLinkName + " operation: " + operation);
                }
            } //TODO what if NODE type is not ELEMENT_NODE
        } else if (task.getLocalName().equals("localNotification")) {
            activityType = InteractionType.NOTIFICATION;
            log.warn(task.getLocalName() + " is not supported yet!");
            throw new RuntimeException(task.getLocalName() + " is not supported yet!");
        } else if (task.getLocalName().equals("localTask")) {
            activityType = InteractionType.TASK;
            log.warn(task.getLocalName() + " is not supported yet!");
            throw new RuntimeException(task.getLocalName() + " is not supported yet!");
        }

        du = new DeploymentUnitDir(new File(extensionContext.getDUDir()));
    }

    private void deriveServiceEPR () throws FaultException {
        DeployDocument deployDocument = du.getDeploymentDescriptor();
        BpelRuntimeContext runTimeContext = extensionContext.getInternalInstance();

        //TODO neeed to extend ExtentionContext
        OProcess oProcess = runTimeContext.getProcessModel();

        TDeployment.Process hiProcess = null;
        List<TDeployment.Process> processList = deployDocument.getDeploy().getProcessList();
        for (TDeployment.Process process : processList) {
            if (process.getName().equals(oProcess.getQName())) {
                hiProcess = process;
                break;
            }
        }

        if (hiProcess == null) {
            throw new FaultException(new QName(B4PExtensionBundle.NS, "related process not found"));
        }

        List<TInvoke> tInvokeList = hiProcess.getInvokeList();
        for (TInvoke tInvoke : tInvokeList) {
            if (tInvoke.getPartnerLink().equals(partnerLinkName)) {
                serviceName = tInvoke.getService().getName();
                servicePort = tInvoke.getService().getPort();
                break;
            }
        }

        if (serviceName == null || servicePort == null) {
            log.error("service and port for human interaction is not found in the deploy.xml");
            throw new FaultException(new QName(B4PExtensionBundle.NS,
                    "service and port for human interaction is not found in the deploy.xml"));
        }

        //get the callback information for the TASK
        if (activityType.equals(InteractionType.TASK)) {
            List<TProvide> tProvideList = hiProcess.getProvideList();
            for (TProvide tProvide : tProvideList) {
                if (tProvide.getPartnerLink().equals(partnerLinkName)) {
                    callbackServiceName = tProvide.getService().getName();
                    callbackServicePort = tProvide.getService().getPort();
                    break;
                }
            }
            if (callbackServiceName == null || callbackServicePort == null) {
                throw new FaultException(new QName(B4PExtensionBundle.NS,
                        "service and port for human task callback is not found in the deploy.xml"));
            }
        }

        hiWSDL = du.getDefinitionForService(serviceName);

        Service service = hiWSDL.getService(serviceName);
        Port port = service.getPort(servicePort);
        List extList = port.getExtensibilityElements();
        for (Object extEle : extList) {
             if (extEle instanceof SOAPAddressImpl) {
                 SOAPAddressImpl soapAddress = (SOAPAddressImpl)extEle;
                 serviceURI = soapAddress.getLocationURI();
                 break;
             }
        }

        if (serviceURI == null) {
            throw new FaultException(new QName(B4PExtensionBundle.NS, "Service URI is not available"));
        }
    }
}
